
<?php

class MessageBase {

}

include "../../src-gen/php/messages.php";
include "../../src-gen/php/PostgreSQLMatcher.php";

class Encoder {

	var $buffer;
	var $index;

    function encode($message) {
        $this->buffer = "";
		$this->index  = 0;
        $message->encodeOn($this);
        return $this->buffer;
    }

	function pushInt($size, $value) {
		#echo "int[" . $this->index . "] = (" . $size . ")" . $value . "\n";
		for ($i = $size/8-1; $i >= 0; --$i) {
			$this->pushByte(($value>>($i*8)) & 0xFF);
		}
	}
	
	function pushInts($size, $values) {
		foreach ($values as $v) {
			$this->pushInt($size, $v);
		}
	}

	function pushString($str) {
		#echo "string[" . $this->index . "] = " . $str . "\n";
		for ($i = 0; $i < strlen($str); ++$i) {
			$this->pushByte(ord($str[$i]));
		}
		$this->pushByte(0);
	}

	function pushByte($value) {
		#echo "byte[" . $this->index . "] = " . bin2hex(chr($value)) . "\n";
		if ($this->index < strlen($this->buffer)) {
			$this->buffer[$this->index] = chr($value);
		} else {
			$this->buffer .= chr($value);
		}
		$this->index += 1;
	}
	
	function pushBytes($values) {
		foreach ($values as $v) {
			$this->pushByte($v);
		}
	}

	function pushLength($size) {
		#echo "length[" . $this->index . "] = " . $size . "\n";
		$this->lengthPosition = $this->index;
		$this->lengthSize = $size;
		$this->pushInt($size, 0);
	}

	function finishMessage() {
		#echo "finish[" . $this->index . "] = " . $size . "\n";
		$length = $this->index;
		if ($this->lengthPosition >= 0) {
			$this->index = $this->lengthPosition;
			$this->pushInt($this->lengthSize, $length - $this->lengthPosition);
		}
	}

}

class InputBuffer {

	var $sock;
	var $buffer;
	var $index;

	function __construct($sock) {
		$this->sock = $sock;
		$this->index = 0;
		$this->buffer = "";
	}

	function refill() {
		$this->index = 0;
		$info = stream_get_meta_data($this->sock);
		$toBeRead = $info["unread_bytes"];
		if ($toBeRead <= 0)
			$toBeRead = 512;
		$this->buffer = fread($this->sock, $toBeRead);
		//echo "refill[".bin2hex($this->buffer)."]\n";
	}

	function get($position, $size) {
		if ($this->index >= strlen($this->buffer)) $this->refill();
		$position /= 8;
		$size /= 8;
		$position += $this->index;
		$value = 0;
		for ($i = 0; $i < $size; ++$i) {
			$value = ($value << 8) | ord($this->buffer[$position + $i]);
		}
		#echo "get[" . $position . "," . $size . "] = " . $value . "\n";
		return $value;
	}

	function skip($size) {
		if ($this->index >= strlen($this->buffer)) $this->refill();
		$this->index += $size / 8;
	}

	function getByte() {
		if ($this->index >= strlen($this->buffer)) $this->refill();
		$val = ord($this->buffer[$this->index++]);
		return $val;
	}

	function getBytes($length) {
		if ($length <= 0) return null;
		$data = array();
		for ($i = 0; $i < $length; ++$i) {
			$data[] = $this->getByte();
		}
		return $data;
	}

	function getString() {
		$value = "";
		while (ord($this->buffer[$this->index]) != 0) {
			$value .= chr($this->getByte());
		}
		++$this->index;
		return $value;
	}

	function getInts($size, $length) {
		$data = array();
		for ($i = 0; $i < $length; ++$i) {
			$data[] = $this->getInt($size);
		}
		return $data;
	}

	function getInt($size) {
		$size /= 8;
		$value = 0;
		for ($i = 0; $i < $size; ++$i) {
			$value = ($value << 8) | $this->getByte();
		}
		return $value;
	}
}

define(DISCONNECTED, 0);
define(READY, 1);
define(EXEC, 2);

class PostgresDriver {

	var $properties;
	var $parameters;
	var $sock;
	var $encoder;
	var $matcher;
	var $buffer;
	var $state;

	function __construct($properties) {
		$this->properties = $properties;
		$this->encoder = new Encoder();
		$this->matcher = new PostgreSQLMatcher();
		$this->state = UN_INIT;
		$this->start();
	}
		
	function start() {

		$this->sock = fsockopen($this->properties["hostname"], 5432);

		$this->buffer = new InputBuffer($this->sock);

		$parameters = array();
		$parameters[] = new StartupParameter("user", $this->properties["user"]);
		$parameters[] = new StartupParameter("database", $this->properties["database"]);
		$mes = new StartupMessage($parameters);
		
		$data = $this->encoder->encode($mes);
		#echo "send: ".bin2hex($data)."\n";
		fwrite($this->sock, $data);
		
		$this->processMessages();
	}

	function processMessages() {
		while ($this->state != READY) {
			$m = $this->matcher->match($this->buffer);
			$m->visit($this);
		}
	}

	function execQuery($query) {
		if ($this->state != READY)
			return null;
		$mes = new Query($query);
		$data = $this->encoder->encode($mes);
		fwrite($this->sock, $data);

		$this->resultTypes = array();
		$this->results = array();
		$this->state = EXEC;

		$this->processMessages();

		return $this->results;
	}

	function close() {
		$mes = new Terminate();
		$data = $this->encoder->encode($mes);
		fwrite($this->sock, $data);
		fclose($this->sock);
	}

	function visitAuthenticationMD5Password($message) {
		$newValue = md5(
			md5($this->properties["password"] . $this->properties["user"])
			. $this->arrayToString($message->getSalt()));
		$mes = new PasswordMessage("md5" . $newValue);
		$data = $this->encoder->encode($mes);
		fwrite($this->sock, $data);
	}

	function arrayToString($arr) {
		$buffer = "";
		foreach ($arr as $a) {
			$buffer .= chr($a);
		}
		return $buffer;
	}

	function visitAuthenticationOk($message) {
	}

	function visitParameterStatus($message) {
		$this->parameters[$message->getParameterName()]
			= $message->getParameterValue();

		//echo $message->getParameterName() . " -> " .
		//	$message->getParameterValue() . "\n";
	}

	function visitBackendKeyData($message) {
	}

	function visitReadyForQuery($message) {
		$this->state = READY;
	}

	function visitRowDescription($message) {
		foreach ($message->getFields() as $field) {
			$type = array($field->getName(),
					$field->getTypeOID(), $field->getFormat());
			$this->resultTypes[] = $type;
		}
	}

	function visitDataRow($message) {
		$res = array();
		foreach ($message->getColumns() as $i => $column) {
			$res[$this->resultTypes[$i][0]] = $this->decodeValue(
				$column->getData(), $this->resultTypes[$i]);
		}
		$this->results[] = $res;
	}

	function visitCommandCompletion($message) {
	}

	function decodeValue($data, $type) {
		if ($type[2] != 0)
			throw Exception("unsupported format");
		if ($type[1] == 19) {
			// string
			$res = "";
			for ($i = 0; $i < count($data); $i++) {
				$res .= chr($data[$i]);
			}
			//echo "string: " . $res . "\n";
			return $res;
		} else if ($type[1] == 16) {
			//echo "bool: " . print_r((bool)($data[0] != 'f')) . "\n";
			return (bool)($data[0] != 102);
		} else {
			throw Exception("unsupported type");
		}
	}
}

$properties = array(
	"hostname" => "localhost",
	"user" => "ludo",
	"password" => "frac power",
	"database" => "ludo",
);
$conn = new PostgresDriver($properties);
$res = $conn->execQuery("SELECT * FROM pg_tables");
$conn->close();
echo "<pre>" . print_r($res, true) . "</pre>\n";


